﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using DevIsMagic.ViewModel;
using System;


namespace DevIsMagic.Wpf.Controls
{
	public class GridViewColumnCollectionProxy : FrameworkElement, INotifyPropertyChanged
	{


		private Dictionary<ColumnViewModel, GridViewColumn> ColumnsMappings { get; set; }
		public GridViewColumnCollectionProxy()
		{
			this.Visibility = Visibility.Collapsed;
			this.ColumnsMappings = new Dictionary<ColumnViewModel, GridViewColumn>();
		}

		#region Properties

		public ObservableCollection<ColumnViewModel> Source
		{
			get { return (ObservableCollection<ColumnViewModel>)GetValue(SourceProperty); }
			set { SetValue(SourceProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty SourceProperty =
			DependencyProperty.RegisterAttached(
				"Source",
				typeof(ObservableCollection<ColumnViewModel>),
				typeof(GridViewColumnCollectionProxy),
				new UIPropertyMetadata(new PropertyChangedCallback(Source_PropertyChangedCallback))
				);



		public GridViewColumnCollection Target
		{
			get { return (GridViewColumnCollection)GetValue(TargetProperty); }
			set { SetValue(TargetProperty, value); }
		}

		// Using a DependencyProperty as the backing store for Target.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty TargetProperty =
			DependencyProperty.Register(
				"Target",
				typeof(GridViewColumnCollection),
				typeof(GridViewColumnCollectionProxy),
				new UIPropertyMetadata(new PropertyChangedCallback(Target_PropertyChangedCallback))
				);


		private static void Target_PropertyChangedCallback(
			DependencyObject d,
			DependencyPropertyChangedEventArgs e
		)
		{
			GridViewColumnCollectionProxy proxy = (GridViewColumnCollectionProxy)d;
			proxy.CleanUp(proxy.Source, (GridViewColumnCollection)e.OldValue);
			proxy.BuildColumns();
		}



		private static void Source_PropertyChangedCallback(
			DependencyObject d,
			DependencyPropertyChangedEventArgs e
		)
		{
			GridViewColumnCollectionProxy proxy = (GridViewColumnCollectionProxy)d;
			proxy.CleanUp((ObservableCollection<ColumnViewModel>)e.OldValue, proxy.Target);
			proxy.BuildColumns();
		}



		#endregion

		private void BuildColumns()
		{
			if (this.Source != null && this.Target != null)
			{
				foreach (var col in this.Source)
				{
					GridViewColumn newGVCol = BuildNewColumn(col);
					if (col.Visible) this.Target.Add(newGVCol);

				}
				this.Target.CollectionChanged += new NotifyCollectionChangedEventHandler(Target_CollectionChanged);
				this.Source.CollectionChanged += new NotifyCollectionChangedEventHandler(Source_CollectionChanged);
			}
		}

		// this is not working :
		//private BindingBase m_widthBinding;
		//public BindingBase WidthBinding
		//{
		//    get { return m_widthBinding; }
		//    set{
		//        if (value != m_widthBinding)
		//        {							
		//            m_widthBinding = value;
		//            OnPropertyChanged("WidthBinding");

		//            if (this.Target != null)
		//            {
		//                foreach (GridViewColumn gvCol in this.Target)
		//                {							
		//                    BindingOperations.SetBinding(
		//                        gvCol,
		//                        GridViewColumn.WidthProperty,
		//                        value
		//                        );
		//                }
		//            }
		//        }
		//    }
		//}		

		private GridViewColumn BuildNewColumn(ColumnViewModel col)
		{
			GridViewColumn newGVCol = new GridViewColumn();


			Binding bWidth = new Binding("Width");
			bWidth.Source = col;
			bWidth.Mode = BindingMode.TwoWay;
			BindingOperations.SetBinding(
				newGVCol,
				GridViewColumn.WidthProperty,
				bWidth
				);
			// this is not working
			//if (WidthBinding != null)
			//{
			//    BindingOperations.SetBinding(
			//        newGVCol,
			//        GridViewColumn.WidthProperty,
			//        WidthBinding
			//        );
			//}
			Binding bHeader = new Binding("Header");
			bHeader.Source = col;
			bHeader.Mode = BindingMode.TwoWay;			
			BindingOperations.SetBinding(
				newGVCol,
				GridViewColumn.HeaderProperty,
				bHeader
				);

			// Don't know if DisplayMemberBinding can be a binding itself
			newGVCol.DisplayMemberBinding = new Binding(col.MemberName);

			this.ColumnsMappings.Add(col, newGVCol);

			col.PropertyChanged += new PropertyChangedEventHandler(SourceCol_PropertyChanged);

			return newGVCol;
		}

		private void CleanUp(ObservableCollection<ColumnViewModel> source, GridViewColumnCollection target)
		{
			this.ColumnsMappings.Clear();
			CleanUp(source);
			CleanUp(target);
		}
		private void CleanUp(ObservableCollection<ColumnViewModel> source)
		{
			if (source != null)
			{
				foreach (var col in source)
				{
					col.PropertyChanged -= new PropertyChangedEventHandler(SourceCol_PropertyChanged);
				}
				source.CollectionChanged -= new NotifyCollectionChangedEventHandler(Source_CollectionChanged);
			}

		}
		private void CleanUp(GridViewColumnCollection target)
		{
			if (target != null)
			{
				foreach (var gvCol in target)
				{
					BindingOperations.ClearAllBindings(gvCol);
				}
				target.CollectionChanged -= new NotifyCollectionChangedEventHandler(Target_CollectionChanged);
			}
		}

		private void SourceCol_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			// Handle the special case of the "visible" property
			if (e.PropertyName == "Visible")
			{
				this.Target.CollectionChanged -= new NotifyCollectionChangedEventHandler(Target_CollectionChanged);

				ColumnViewModel col = (ColumnViewModel)sender;
				GridViewColumn gvCol = this.ColumnsMappings[col];
				if (col.Visible)
				{
					this.Target.Insert(
						this.Source.Where((c) => c.Visible).ToList().IndexOf(col), // find the index in visibles items
						gvCol
						);
					// force update of the width to make the column resize
					double temp = gvCol.Width;
					gvCol.Width = 0;
					gvCol.Width = temp;
				}
				else
				{
					this.Target.Remove(gvCol);
				}
				this.Target.CollectionChanged += new NotifyCollectionChangedEventHandler(Target_CollectionChanged);
			}
		}

		private void Source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			this.Target.CollectionChanged -= new NotifyCollectionChangedEventHandler(Target_CollectionChanged);
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					for (int i = 0; i < e.NewItems.Count; i++)
					{
						ColumnViewModel col = (ColumnViewModel)e.NewItems[i];
						GridViewColumn newGVCol = BuildNewColumn(col);
						if (col.Visible)
						{
							this.Target.Insert(
								e.NewStartingIndex + i,
								newGVCol
								);
						}
					}
					break;
				case NotifyCollectionChangedAction.Move:
					for (int i = 0; i < e.NewItems.Count; i++)
					{
						this.Target.RemoveAt(e.OldStartingIndex);
						this.Target.Insert(e.NewStartingIndex + i, (GridViewColumn)e.NewItems[i]);

					}
					break;
				case NotifyCollectionChangedAction.Remove:
					for (int i = 0; i < e.OldItems.Count; i++)
					{
						BindingOperations.ClearAllBindings(this.Target[e.OldStartingIndex]);
						this.Target.RemoveAt(e.OldStartingIndex);
					}
					break;
				case NotifyCollectionChangedAction.Replace:
					for (int i = 0; i < e.NewItems.Count; i++)
					{
						BindingOperations.ClearAllBindings(this.Target[e.OldStartingIndex]);
						this.Target[e.NewStartingIndex + i] = (GridViewColumn)e.NewItems[i];
					}
					break;
				case NotifyCollectionChangedAction.Reset:
					foreach (var gvCol in this.Target)
					{
						BindingOperations.ClearAllBindings(this.Target[e.OldStartingIndex]);
					}
					this.Target.Clear();
					break;
			}
			this.Target.CollectionChanged += new NotifyCollectionChangedEventHandler(Target_CollectionChanged);
		}

		private void Target_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			this.Source.CollectionChanged -= new NotifyCollectionChangedEventHandler(Source_CollectionChanged);
			switch (e.Action)
			{
				// Only the move action should be handled
				case NotifyCollectionChangedAction.Move:
					for (int i = 0; i < e.NewItems.Count; i++)
					{
						var visibles = this.Source.Where((c) => c.Visible).ToList();

						var movedCol = visibles[e.OldStartingIndex];
						var dropPos = visibles[e.NewStartingIndex + i];

						int realOldIndex = this.Source.IndexOf(movedCol);
						int realNewIndex = this.Source.IndexOf(dropPos);

						this.Source.Move(realOldIndex, realNewIndex);
						//this.Source.RemoveAt(realOldIndex);
						//this.Source.Insert(realNewIndex + i, movedCol);

					}
					break;
			}
			this.Source.CollectionChanged += new NotifyCollectionChangedEventHandler(Source_CollectionChanged);
		}



		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged(string propertyName)
		{
			EnsureIsOnUIThread();
			if (PropertyChanged != null)
			{
				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		[Conditional("DEBUG")]
		private void EnsureIsOnUIThread()
		{
			Debug.Assert(Dispatcher.CurrentDispatcher == this.Dispatcher, "Call must be made on UI thread.");
		}
		#endregion
	}
}
